package com.socialin.picsin.camera.view;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.graphics.Bitmap.Config;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.BitmapFactory.Options;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.PictureCallback;
import android.hardware.Camera.ShutterCallback;
import android.hardware.Camera.Size;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.SurfaceHolder;
import android.view.ViewGroup.LayoutParams;

public class CameraViewEclair extends CameraViewDonut {
	
	private ArrayList<SupportedSize> supportedSizes;
	
	private int optimalPictureWidth = 640;
	private int optimalPictureHeight = 480;
	private PictureCallback pictureCallback;
	private Rect takenPictureRect = new Rect();
	private CameraCallBack cameraCallBack;
	private int leftOffset = 0;
	private int topOffset = 0;
	private int lastWidth = -1;
	private int lastHeight = -1;
	
	
	public CameraViewEclair(Context context, LayoutParams layoutParams) {
		super(context, layoutParams);
		supportedSizes = new ArrayList<CameraViewEclair.SupportedSize>();
		pictureCallback = new PictureCallback() {
			
			@Override
			public void onPictureTaken(byte[] data, Camera camera) {
				Log.e("ex", "onPictureTaken");
				Options options = new Options();
				options.inPreferredConfig = Config.ARGB_8888;
				options.inSampleSize = 1;
				Bitmap takenBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);
				
				Bitmap takenRectBitmap = Bitmap.createBitmap(takenPictureRect.width(),takenPictureRect.height(),Config.ARGB_8888);
				Canvas canvas = new Canvas(takenRectBitmap);
				Paint paint = new Paint();
				paint.setAntiAlias(true);
				paint.setFilterBitmap(true);
				canvas.drawBitmap(takenBitmap, -takenPictureRect.left, -takenPictureRect.top, paint);
				takenBitmap.recycle();
				takenBitmap = null;
				cameraCallBack.setTakenCropedBitmap(takenRectBitmap);
				//customDrawView.addCameraBitmap(takenRectBitmap);
			}
		};
	}

	public CameraViewEclair(Context context, AttributeSet attrs) {
		super(context, attrs);
	}
	
	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		super.surfaceCreated(holder);
		initSupportedSizes();
		
	}
	
	public void setCameraCallBack(CameraCallBack cameraCallBack)
	{
		this.cameraCallBack = cameraCallBack;
	}
	
	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
		super.surfaceChanged(holder, format, width, height);
		if(width == lastWidth && height == lastHeight)
		{
			return;
		}
		//initCameraSize();
		
		if(params != null)
		try{
			initBestSupportedSizes(params,width,height);
			Size size = params.getPreviewSize();
			//Size size = getOptimalPreviewSize(params.getSupportedPreviewSizes(), width, height);
			if(size != null){
				//params.setPreviewSize(size.width, size.height);
				camera.setParameters(params);
				
				float scale = 1;
				float scaleX = (float)width / (float)size.width;
				float scaleY = (float)height / (float)size.height;
				scale = scaleX < scaleY ? scaleX : scaleY;
				int newWidth = (int)(size.width * scale);
				int newHeight = (int)(size.height*scale);
				Log.d(TAG, "preview scale: " + scale);
				lastWidth = width;
				lastHeight = height;
				Log.e("ex", "newWidth = "+newWidth);
				Log.e("ex", "newHeight = "+newHeight);
				Log.e("ex", "width = "+width);
				Log.e("ex", "height = "+height);
				if(newWidth < width)
				{
					lastWidth = width;
					scaleY = (float)newHeight/(float)newWidth;
					lastHeight = (int)((float)lastWidth*scaleY);
				}
				else
				{
					lastHeight = height;
					scaleX = (float)newWidth/(float)newHeight;
					lastWidth = (int)((float)lastHeight*scaleX);
				}
				leftOffset = (lastWidth-width)/2;
				topOffset = (lastHeight-height)/2;
				Log.e("ex", "leftOffset = "+leftOffset);
				Log.e("ex", "topOffset = "+topOffset);
				Log.e("ex", "lastWidth = "+lastWidth);
				Log.e("ex", "lastHeight = "+lastHeight);
				android.widget.FrameLayout.LayoutParams layoutParams = new android.widget.FrameLayout.LayoutParams(lastWidth, lastHeight);
				layoutParams.gravity = Gravity.CENTER;
				this.setLayoutParams(layoutParams);
				Log.d(TAG, "previewWidth: " + size.width + " previewHeight: " + size.height);
				Log.d(TAG, "displayWidth: " + width + " displayHeight: " + height);
			}
		} catch (Exception e) {
			sendParams(e);
		}
	}
	
	private void initSupportedSizes()
	{
		supportedSizes = new ArrayList<CameraViewEclair.SupportedSize>();
		List<Size> previewSizes = camera.getParameters().getSupportedPreviewSizes();
		List<Size> pictureSizes = camera.getParameters().getSupportedPictureSizes();
		
		for(int i=0;i<previewSizes.size();i++)
		{
			Size previewSize = previewSizes.get(i);
			float previewProp = ((float)previewSize.width)/((float)previewSize.height);
			for(int j=0;j<pictureSizes.size();j++)
			{
				
				Size pictureSize = pictureSizes.get(j);
				float pictureProp = ((float)pictureSize.width)/((float)pictureSize.height);
				if(previewProp == pictureProp)
				{
					supportedSizes.add(new SupportedSize(previewSize, pictureSize));
					Log.e("ex", "previewSize width : "+previewSize.width+" height:"+previewSize.height);
					Log.e("ex", "pictureSize width : "+pictureSize.width+" height:"+pictureSize.height);
				}
			}
		}
		
		
	}
	
	private Size getCameraPictureSize()
	{
			
		for(int i=0;i<supportedSizes.size();i++)
		{
			Size pictureSize = supportedSizes.get(i).getPictureSize();
			if(pictureSize.width==optimalPictureWidth && pictureSize.height == optimalPictureHeight)
			{
				return pictureSize;
			}
		}
		
		for(int i=0;i<supportedSizes.size();i++)
		{
			Size pictureSize = supportedSizes.get(i).getPictureSize();
			if(pictureSize.width >=optimalPictureWidth && pictureSize.width<=optimalPictureWidth+256)
			{
				return pictureSize;
			}
		}
		
		return supportedSizes.get(0).getPictureSize();
	}
	
	private Size initAndGetOptimalPreviewSize(ArrayList<Size> sizes, int w, int h) {
	    final double ASPECT_TOLERANCE = 0.05;
	    double targetRatio = (double) w / h;
	    if (sizes == null) return null;

	    Size optimalSize = null;
	    double minDiff = Double.MAX_VALUE;

	    int targetHeight = h;

	    // Try to find an size match aspect ratio and size
	    for (Size size : sizes) {
	        double ratio = (double) size.width / size.height;
	        if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
	        if (Math.abs(size.height - targetHeight) < minDiff) {
	            optimalSize = size;
	            minDiff = Math.abs(size.height - targetHeight);
	        }
	    }

	    // Cannot find the one match the aspect ratio, ignore the requirement
	    if (optimalSize == null) {
	        minDiff = Double.MAX_VALUE;
	        for (Size size : sizes) {
	            if (Math.abs(size.height - targetHeight) < minDiff) {
	                optimalSize = size;
	                minDiff = Math.abs(size.height - targetHeight);
	            }
	        }
	    }
	    return optimalSize;
	}

	
	private void initBestSupportedSizes(Parameters params,int width,int height)
	{
		Size bestSupportedPictureSize = getCameraPictureSize();
		Log.e("ex", "bestSupportedPictureSize width:"+bestSupportedPictureSize.width+" height:"+bestSupportedPictureSize.height);
		ArrayList<Size> supportedPreviewSizesWithCurrentPictureSize = new ArrayList<Camera.Size>();
		for(int i=0;i<supportedSizes.size();i++)
		{
			Size pictureSize = supportedSizes.get(i).getPictureSize();
			if(pictureSize.width == bestSupportedPictureSize.width 
					&& pictureSize.height == bestSupportedPictureSize.height)
			{
				supportedPreviewSizesWithCurrentPictureSize.add(supportedSizes.get(i).getPreviewSize());
				Log.e("ex", "supp previewSize width:"+supportedSizes.get(i).getPreviewSize().width+" height:"+supportedSizes.get(i).getPreviewSize().height);
			}
		}
		Size bestSupportedPreviewSize = initAndGetOptimalPreviewSize(supportedPreviewSizesWithCurrentPictureSize, width, height);
		if(bestSupportedPreviewSize == null)
		{
			bestSupportedPreviewSize = supportedPreviewSizesWithCurrentPictureSize.get(0);
		}
		Log.e("ex", "bestSupportedPreviewSize width:"+bestSupportedPreviewSize.width+" height:"+bestSupportedPreviewSize.height);
		params.setPreviewSize(bestSupportedPreviewSize.width, bestSupportedPreviewSize.height);
		params.setPictureSize(bestSupportedPictureSize.width, bestSupportedPictureSize.height);
	}
	
	private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
	    final double ASPECT_TOLERANCE = 0.05;
	    double targetRatio = (double) w / h;
	    if (sizes == null) return null;

	    Size optimalSize = null;
	    double minDiff = Double.MAX_VALUE;

	    int targetHeight = h;

	    // Try to find an size match aspect ratio and size
	    for (Size size : sizes) {
	        double ratio = (double) size.width / size.height;
	        if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
	        if (Math.abs(size.height - targetHeight) < minDiff) {
	            optimalSize = size;
	            minDiff = Math.abs(size.height - targetHeight);
	        }
	    }

	    // Cannot find the one match the aspect ratio, ignore the requirement
	    if (optimalSize == null) {
	        minDiff = Double.MAX_VALUE;
	        for (Size size : sizes) {
	            if (Math.abs(size.height - targetHeight) < minDiff) {
	                optimalSize = size;
	                minDiff = Math.abs(size.height - targetHeight);
	            }
	        }
	    }
	    return optimalSize;
	}
	
	protected void initCameraSize(){
		try {
			List<Size> supportedSizes = params.getSupportedPictureSizes();
			int bestWidthLarge = maxWidth;
			int bestHeightLarge = maxHeight;
//			int bestWidthSmall = CameraViewCupcake.minWidth;
//			int bestHeightSmall = CameraViewCupcake.minHeight;
			
			
			
			int supportedBestWidth = Integer.MAX_VALUE;
			int supportedBestHeight = Integer.MAX_VALUE;
			boolean isBestInitialized = false;
			for(int i = 0; i < supportedSizes.size(); i++){
				Size cur = supportedSizes.get(i);
				if(cur.width == bestWidthLarge && cur.height == bestHeightLarge){
					isBestInitialized = true;
					supportedBestWidth = cur.width;
					supportedBestHeight = cur.height;
				}
//				else if(cur.width == bestWidthSmall && cur.height == bestHeightSmall && !isBestInitialized){
//					isBestInitialized = true;
//					supportedBestWidth = cur.width;
//					supportedBestHeight = cur.height;
//				}
			}
			if(!isBestInitialized){
				float bestRatio = (float)bestWidthLarge / (float) bestHeightLarge;
				int bestRatioWidth = Integer.MAX_VALUE;
				int bestRatioHeight = Integer.MAX_VALUE;
				int bestRatioWidthSmall = Integer.MAX_VALUE;
				int bestRatioHeightSmall = Integer.MAX_VALUE;
//				float bestRatioSmall = (float)bestWidthSmall / (float) bestHeightSmall;
				for(int i = 0; i < supportedSizes.size(); i++){
					Size cur = supportedSizes.get(i);
					float ratioCur = (float)cur.width / (float)cur.height;
					if(Math.abs(ratioCur - bestRatio) < 0.01f && cur.width >= bestWidthLarge && cur.width < bestRatioWidth){
						bestRatioWidth = cur.width;
						bestRatioHeight = cur.height;
					}
//					if(Math.abs(ratioCur - bestRatioSmall) < 0.01f && cur.width >= bestWidthSmall && cur.width < bestRatioWidth){
//						bestRatioWidthSmall = cur.width;
//						bestRatioHeightSmall = cur.height;
//					}
				}
				if(bestRatioWidth != Integer.MAX_VALUE){
					isBestInitialized = true;
					supportedBestWidth = bestRatioWidth;
					supportedBestHeight = bestRatioHeight;
				}
//				else if(bestRatioWidthSmall != Integer.MAX_VALUE){
//					isBestInitialized = true;
//					supportedBestWidth = bestRatioWidthSmall;
//					supportedBestHeight = bestRatioHeightSmall;
//				}
			}
			if(isBestInitialized){
				Log.d(TAG, "supportedBestWidth: " + supportedBestWidth + " supportedBestHeight: " + supportedBestHeight);
				params.setPictureSize(supportedBestWidth, supportedBestHeight);
				try{
					camera.setParameters(params);
				}
				catch (Exception e) {
					sendParams(e);
				}
			}
		} catch (Exception e) {
		}
	}
	
//	@Override
//	public void takePicture(final ShutterCallback shutter, final PictureCallback raw, final PictureCallback jpeg) {
//		if (canTakePic && !isPicTaking && camera != null){
//			isPicTaking = true;
//			camera.autoFocus(new AutoFocusCallback() {
//				
//				@Override
//				public void onAutoFocus(boolean success, Camera camera) {
//					if(!isPlayShutter){
//						AudioManager mgr = (AudioManager)getContext().getSystemService(Context.AUDIO_SERVICE);
//					    mgr.setStreamMute(AudioManager.STREAM_SYSTEM, true);
//					}
//					if(!isReleased){
//						camera.takePicture(shutter, raw, jpeg);
//						camera.cancelAutoFocus();
//					}
//				}
//			});
//		}
//	}

	@Override
	public int getFlashMode() {
		try{
			String flashMode = params.getFlashMode();
			if(flashMode == null)
				return FLASH_MODE_INVALID;
			if (flashMode.equals(Camera.Parameters.FLASH_MODE_ON)) return FLASH_MODE_ON;
			else if (flashMode.equalsIgnoreCase(Camera.Parameters.FLASH_MODE_OFF)) {
				return FLASH_MODE_OFF;
			} else if (flashMode.equalsIgnoreCase(Camera.Parameters.FLASH_MODE_AUTO)) { return FLASH_MODE_AUTO; }
			return FLASH_MODE_INVALID;
		}catch (Exception e) {
			Log.e(TAG, "exception when getting flash mode", e);
			return FLASH_MODE_INVALID;
		}
	}

	public void setFlashMode(int flashMode) {
		if (flashMode == FLASH_MODE_ON) params.setFlashMode(Camera.Parameters.FLASH_MODE_ON);
		else if (flashMode == FLASH_MODE_OFF) {
			params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
		} else if (flashMode == FLASH_MODE_AUTO) {
			params.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
		}
	}

	@Override
	public int[] getFlashModeValues() {
		List<String> flashModeValues = params.getSupportedFlashModes();
		if (flashModeValues != null) {
			if(flashModeValues.size() == 1)
				return null;
			int[] result = { FLASH_MODE_INVALID, FLASH_MODE_INVALID, FLASH_MODE_INVALID };
			for (int i = 0; i < flashModeValues.size(); i++) {
				if ((flashModeValues.get(i)).equalsIgnoreCase(Camera.Parameters.FLASH_MODE_ON)) {
					result[0] = FLASH_MODE_ON;
				} else if ((flashModeValues.get(i)).equalsIgnoreCase(Camera.Parameters.FLASH_MODE_OFF)) {
					result[1] = FLASH_MODE_OFF;
				} else if ((flashModeValues.get(i)).equalsIgnoreCase(Camera.Parameters.FLASH_MODE_AUTO)) {
					result[2] = FLASH_MODE_AUTO;
				}
			}
			return result;
		}
		return null;
	}
	
	@Override
	public String[] getEffects() {
		List<String> values = params.getSupportedColorEffects();
		if (values != null) return values.toArray(new String[values.size()]);
		return null;
	}

	@Override
	public void setEffect(String value) {
		params.setColorEffect(value);
	}

	@Override
	public String getEffect() {
		return params.getColorEffect();
	}
	
	@Override
	public String[] getSceneModeValues() {
		List<String> values = params.getSupportedSceneModes();
		if (values != null) return values.toArray(new String[values.size()]);
		return null;
	}

	@Override
	public void setSceneMode(String value) {
		params.setSceneMode(value);
	}

	@Override
	public String getSceneMode() {
		return params.getSceneMode();
	}
	
	@Override
	public String[] getFocusModeValues() {
		List<String> values = params.getSupportedFocusModes();
		if (values != null) {
			String[] result = values.toArray(new String[values.size()]);
			return result;
		}
		return null;
	}

	@Override
	public void setFocusMode(String value) {
		params.setFocusMode(value);
	}

	public String getFocusMode() {
		return params.getFocusMode();
	}
	
	@Override
	public String[] getWhiteBalanceValues() {
		List<String> values = params.getSupportedWhiteBalance();
		if (values != null) { return values.toArray(new String[values.size()]); }

		return null;
	}

	@Override
	public void setWhiteBalance(String value) {
		params.setWhiteBalance(value);
	}

	@Override
	public String getWhiteBalance() {
		return params.getWhiteBalance();
	}
	
	@Override
	public String[] getPictureSizeValues() {
		List<Camera.Size> values = params.getSupportedPictureSizes();
		if (values != null) {
			String[] result = new String[values.size()];
			for(int i = 0; i < result.length; i++){
				result[i] = values.get(i).width + "x" + values.get(i).height;
			}
			return result;
		}
		return null;
	}

	@Override
	public String getPictureSize() {
		return params.getPictureSize().width + "x" + params.getPictureSize().height;
	}
	
	@Override
	public void setRotation(int value) {
		params.setRotation(value);
	}
	
	@Override
	public void setImageSize(int width, int height) {
		super.setImageSize(width, height);
		initCameraSize();
	}
	
	public void takePictureAndGetRectBitmap(Rect rect)
	{
		Log.e("ex", "previewRun = "+previewRunning);
		if(!previewRunning)
		{
			return;
		}
		Log.e("ex", "leftOffset = "+leftOffset);
		Log.e("ex","topOffset = "+topOffset);
		rect = new Rect(leftOffset+rect.left, topOffset+rect.top, leftOffset+rect.right, topOffset+rect.bottom);
		Size pictureSize = camera.getParameters().getPictureSize();
		float scale = (float)pictureSize.width/(float)getWidth();
		int takenPictureRectLeft = (int)((float)rect.left*scale);
		int takenPictureRectTop = (int)((float)rect.top*scale);
		int takePictureRectWidth = (int)((float)rect.width()*scale);
		int takePictureRectHeight = (int)((float)rect.height()*scale);
		takenPictureRect = new Rect(takenPictureRectLeft, takenPictureRectTop, takenPictureRectLeft+takePictureRectWidth, takenPictureRectTop+takePictureRectHeight);
		takePicture(null, null, pictureCallback);
		previewRunning = false;
	}
	
	public void startPreview()
	{
		camera.startPreview();
		previewRunning = true;
		Log.e("ex", "startPreview");
		canTakePic = true;
		setPictureTaking(false);
	}
	
	public void stopPreview()
	{
		camera.stopPreview();
		previewRunning = false;
	}
	
	private class SupportedSize
	{
		public SupportedSize(Size previewSize,Size pictureSize)
		{
			this.previewSize = previewSize;
			this.pictureSize = pictureSize;
		}
		Size previewSize;
		Size pictureSize;
		public Size getPictureSize()
		{
			return pictureSize;
		}
		public Size getPreviewSize()
		{
			return previewSize;
		}
	}
}
